#ifndef TDD_H
#define TDD_H

/*
what we want:

TESTCLASS(SomeClass)
{
    virtual void ClassInitialize() { TDD_VERIFY(<expression>); } // once per class, before all tests
    virtual void ClassCleanup   () { TDD_VERIFY(<expression>); } // once per class, after all tests
    virtual void TestInitialize () { TDD_VERIFY(<expression>); } // before each test
    virtual void TestCleanup    () { TDD_VERIFY(<expression>); } // after each test

    TESTMETHOD(SomeTest)
    {
        TDD_VERIFY(<expression>);
    }
    TESTMETHOD(AnotherTest)
    {
        TDD_VERIFY(<expression>);
    }
};
*/

#ifndef __in
#error please #include this file after sal.h or specstrings.h
#endif

namespace TDD
{

struct UnitTestInfo // a container to hold some info about each test
{
    UnitTestInfo(__in const char* g, __in const char* t) : group(g), testname(t) {}
    UnitTestInfo(__in const UnitTestInfo* uti) : group(uti->group), testname(uti->testname) {}
    const char *group, *testname;
};

struct TestFailure : public UnitTestInfo // a container to hold some info about a test failure
{
    TestFailure(__in const UnitTestInfo* uti, 
                unsigned long line, 
                __in const char* name, 
                __in const char* error
               ) 
        : UnitTestInfo(uti), 
        line_number(line), 
        file_name(name), 
        error_string(error) 
    {
    }

    const char *file_name, *error_string;
    unsigned long line_number;
};

struct Reporter
{
    virtual void ForEachTest   (const UnitTestInfo& uti) {}  // called once for each test
    virtual void ForEachFailure(const TestFailure& tr) = 0;  // called once for each failure
};
struct Discriminator
{
    virtual bool WantTest(const UnitTestInfo& uti) { return true; } // return true if you want to run this test
};

#ifdef _CPPUNWIND

class TddException
{
    unsigned long _line;
    const char * _file;
    const char * _message;

public:
    TddException(unsigned long line, __in const char * file) 
        : _line(line), 
        _file(file), 
        _message("internal test exception") 
    {}

    TddException(__in const char* message, unsigned long line, __in const char * file) 
        : _line(line), 
        _file(file), 
        _message(message) 
    {}
	virtual ~TddException() {}

    unsigned long GetLine() const { return _line; }
    const char *  GetFile() const { return _file; }
    virtual const char * GetExceptionText() const { return _message; }

protected:
	void SetMessage(__in const char* message) { _message = message; }
	void SetFile   (__in const char* file)    { _file = file; }
};

#define THROW_TDD_EXCEPTION throw TDD::TddException(__LINE__, __FILE__)
#define THROW_TDD_EXCEPTION_MESSAGE(message) throw TDD::TddException(message, __LINE__, __FILE__)

#endif // #ifdef _CPPUNWIND

struct TestClassBase
{
    virtual void ClassInitialize() {} // once per class, before all tests
    virtual void ClassCleanup   () {} // once per class, after all tests
    virtual void TestInitialize () {} // before each test
    virtual void TestCleanup    () {} // after each test
};

class Verifier
{
    static Reporter*& GetReporter()
    {
        static Reporter * s_reporter = 0;
        return s_reporter;
    }
    static UnitTestInfo*& GetUnitTestInfo()
    {
        static UnitTestInfo * s_uti = 0;
        return s_uti;
    }
public:
    static void SetVerifierInfo(__in Reporter * reporter, __in UnitTestInfo* uti)
    {
        GetReporter() = reporter;
        GetUnitTestInfo() = uti;
    }
    static void Verify(int line, __in const char * filename, bool b, __in const char * errorString)
    {
        if (false == b)
        {
        // if exception handling semantics are defined and _TDD_NO_RETURN_ON_ASSERT_FAILURE isn't defined
        // then we throw exceptions to avoid continuing processing in the current execution context
        // if either exception handling semantics are undefined or _TDD_NO_RETURN_ON_ASSERT_FAILURE is defined
        // then we just call the reporter with the failure and do not throw any exceptions
        #if defined(_CPPUNWIND) && !defined(_TDD_NO_RETURN_ON_ASSERT_FAILURE)
            throw TDD::TddException(errorString, line, filename);
        #else
            GetReporter()->ForEachFailure (TestFailure (GetUnitTestInfo(), line, filename, errorString));
        #endif
        }        
    }
};

class ClassRegistrarBase
{
    ClassRegistrarBase* m_pNext;    // pointer to next test class entry
public:
    ClassRegistrarBase() : m_pNext(0) { AddClass(this); }
    static void RunTests(__in Discriminator& d, __in Reporter& r)
    {
        ClassRegistrarBase* p = GetTestTable();
        while (p) {
            p->RunClassTests(d, r);
            p = p->m_pNext;
        }
    }

private:
    virtual void RunClassTests(__in Discriminator& d, __in Reporter& r) = 0;

    static ClassRegistrarBase*& GetTestTable()
    {
        static ClassRegistrarBase * s_table = 0;
        return s_table;
    }
    void AddClass(__in ClassRegistrarBase* t)
    {
        ClassRegistrarBase * p = GetTestTable();
        if (!p)   // empty: add to beginning
            GetTestTable() = t;
        else {
            while (p->m_pNext) p = p->m_pNext;
            p->m_pNext = t;   // add to end
        }
    }
};
typedef ClassRegistrarBase UnitTestBase; // for backwards-compatibility


class BoolReporter : public Reporter
{
    Reporter& m_r;
    bool m_bHasError;
public:
    BoolReporter(__in Reporter& r): m_bHasError(false), m_r(r) {}
    bool HasError() const { return m_bHasError; }

public: // Reporter
    virtual void ForEachTest   (__in const UnitTestInfo& uti) 
    {
        m_r.ForEachTest(uti); 
    }

    virtual void ForEachFailure(__in const TestFailure& tr)
    {
        m_bHasError = true;
        m_r.ForEachFailure(tr);
    }
};


class TryCatch // wraps try/catch block and reporting
{
    UnitTestInfo m_uti;
    Reporter& m_reporter;

public:
    TryCatch(__in Reporter& r, __in const char * group) : m_uti(group, "<not set yet>"), m_reporter(r) {}
    bool TryCatchAndReport(__in const char * testname, __in const char * message)
    {
        BoolReporter br(m_reporter);

        m_uti.testname = testname;
        Verifier::SetVerifierInfo(&br, &m_uti);

    #ifdef _CPPUNWIND
        try {
    #endif
            Execute();
    #ifdef _CPPUNWIND
        } catch (TddException& e) {
            br.ForEachFailure (TestFailure (&m_uti, e.GetLine(), e.GetFile(), e.GetExceptionText()));
        } catch (...) {
            br.ForEachFailure (TestFailure (&m_uti, __LINE__, __FILE__, message));
        }
    #endif

        Verifier::SetVerifierInfo(&m_reporter, &m_uti); // br will go out of scope
        return br.HasError();
    }
private:
    virtual void Execute() = 0; // "template method" design pattern
};

class TestMethodInfo : public UnitTestInfo
{
public:
    TestMethodInfo * m_pNext;
    TestMethodInfo(__in const char* g, __in const char* t) : m_pNext(0), UnitTestInfo(g, t) {}
    virtual void RunTest() = 0;
};

template<typename T> struct TheClassTypedefer { typedef T TheClass; };

template<typename T> class ClassRegistrar : public ClassRegistrarBase
{
public:
    ClassRegistrar(__in const char * classname)
    {
        ClassName() = classname;
    }
    static const char *& ClassName()
    {
        static const char * s_classname = "no class name set!";
        return s_classname;
    }
    static void AddTestMethod(__in TestMethodInfo * tmi)
    {
        TestMethodInfo * p = GetTestMethodTable();
        if (!p)    // empty: add to beginning
            GetTestMethodTable() = tmi;
        else {
            while (p->m_pNext) p = p->m_pNext;
            p->m_pNext = tmi;    // add to end
        }
    }

private:
    static TestMethodInfo *& GetTestMethodTable()
    {
        static TestMethodInfo * s_table = 0;
        return s_table;
    }

    virtual void RunClassTests(__in Discriminator& d, __in Reporter& r)
    {
        class TryCatchMethod : public TryCatch // wraps try/catch/report around a method
        {
            void (TestClassBase::* m_pmfn)(); // ptr-to-method
        public:
            TryCatchMethod(__in void (TestClassBase::*pmfn)(), __in Reporter& r, __in const char * group)
                : TryCatch(r, group), m_pmfn(pmfn)
            {}
        private:
            virtual void Execute() { (GetClassInstance().*m_pmfn)(); } // call the method through the pointer
        };
        
        TestClassBase& test = GetClassInstance();
        TestMethodInfo * p = GetTestMethodTable();
        if (!p)
            return; // no tests to run

        class ClassInitializeOnce // only calls ClassInitialize once, wrapped in a try/catch/report class
        {
            bool m_bInitialized, m_bFailed;
        public:
            ClassInitializeOnce() : m_bInitialized(false), m_bFailed(false) {}
            bool WasInitialized() const { return m_bInitialized; }
            bool ClassInitialize(__in TestClassBase& test, __in Reporter& r, __in const char * group)
            {
                if (m_bInitialized == false)
                {
                    m_bInitialized = true;
                    TryCatchMethod tcm(&TestClassBase::ClassInitialize, r, group);
                    m_bFailed = tcm.TryCatchAndReport("ClassInitialize", "unknown exception from ClassInitialize");
                }
                return m_bFailed;
            }
        } cio;

        while (p) {
            if (d.WantTest(*p)) {
                r.ForEachTest(*p);

                if (true == cio.ClassInitialize(test, r, p->group)) // true == failure in (only) CI call
                {    // CI failed; report that every test can't run
                    r.ForEachFailure(TestFailure(p, __LINE__, __FILE__, "ClassInitialize failure: can't run test!"));
                } else {

                    // TestInitialize
                    TryCatchMethod tcmTI(&TestClassBase::TestInitialize, r, p->group);
                    if (true == tcmTI.TryCatchAndReport("TestInitialize", "unknown exception from TestInitialize"))
                    { // TI failed; report that this test can't run
                        r.ForEachFailure(TestFailure(p, __LINE__, __FILE__, "TestInitialize failure: can't run test!"));
                    } else {

                        // run test
                        class TryCatchTest : private TryCatch // RunTest wrapped in a try/catch/report
                        {
                            TestMethodInfo * m_tmi;
                        public:
                            TryCatchTest(__in TestMethodInfo* p, __in Reporter& r) : TryCatch(r, p->group), m_tmi(p) {}
                            void RunTest() { TryCatchAndReport(m_tmi->testname, "unknown exception:  continuing anyway"); }
                        private:
                            virtual void Execute() { m_tmi->RunTest(); }
                        } tct(p, r);
                        tct.RunTest();
                    }

                    // TestCleanup
                    TryCatchMethod tcmTC(&TestClassBase::TestCleanup, r, p->group);
                    tcmTC.TryCatchAndReport("TestCleanup", "unknown exception from TestCleanup");
                }
            }
            p = p->m_pNext;
        }
        if (true == cio.WasInitialized())
        {   // CI /was/ called
            TryCatchMethod tcm(&TestClassBase::ClassCleanup, r, GetTestMethodTable()->group);
            tcm.TryCatchAndReport("ClassCleanup", "unknown exception from ClassCleanup");
        }
    }

public:
    static T& GetClassInstance()
    {
        static T s_t;
        return s_t;
    }
};

template<typename C, void (*pfnM)(C*), typename MethodNameHolderType> class MethodRegistrar : public TestMethodInfo
{
public:
    MethodRegistrar() : TestMethodInfo(ClassRegistrar<C>::ClassName(), MethodNameHolderType::GetMethodName())
    {
        ClassRegistrar<C>::AddTestMethod(this);
    }
    virtual void RunTest()
    {
        (*pfnM)(&ClassRegistrar<C>::GetClassInstance());
    }
};

template<typename C> class TddAutoPtr // alloc on heap to avoid PREfast warning 22109
{
	C* m_p;
public:
	explicit TddAutoPtr(C* p) : m_p(p) {}
	~TddAutoPtr() { delete m_p; }
};


struct StringUtils
{
	// basically, strcmp() - written here so that tdd.h doesn't include any other headers
	static const char* TrimClassName(__inout_ecount(count) char* namespaces, int count, const char* className)
	{
		for(int i=0; i<count; ++i)
		{
			bool same = true;
			for(int j=0; i+j<count && className[j]; ++j)
			{
				if (namespaces[i+j] != className[j])
				{
					same = false;
					break;
				}
			}
			if (same == true)
			{
				namespaces[i] = 0;
				break;
			}
		}
		return namespaces;
	}
};

} // namespace TDD


#define TESTCLASS(classname) \
	struct classname##_TddNamespaceResolver { static const char* GetNameSpace() { static char s_sig[] = __FUNCTION__; return TDD::StringUtils::TrimClassName(s_sig, sizeof(s_sig), "_TddNamespaceResolver"); } }; \
	class classname; TDD::TddAutoPtr<TDD::ClassRegistrar<classname> > g_##classname##_variable(new TDD::ClassRegistrar<classname>(classname##_TddNamespaceResolver::GetNameSpace())); \
	class classname : public TDD::TestClassBase, private TDD::TheClassTypedefer<classname>

#define TESTMETHOD(methodname) static void MethodRegistrar_##methodname##_Wrapper(TheClass* This) { This->methodname##_test_method(); } \
    struct MethodNameHolder_##methodname{ static const char *GetMethodName() { return #methodname; } };\
    TDD::MethodRegistrar<TheClass, &MethodRegistrar_##methodname##_Wrapper, MethodNameHolder_##methodname> __m_##methodname##_variable; \
    virtual void methodname##_test_method() // virtual to avoid PREfast warning 25007

// if _TDD_NO_RETURN_ON_ASSERT_FAILURE is defined then we don't return inside the TDD_VERIFY macros
// if _CPPUNWIND is defined and _TDD_NO_RETURN_ON_ASSERT_FAILURE is not defined then TDD_VERIFY macros
// throw TDDExceptions instead of returning, however if both _TDD_NO_RETURN_ON_ASSERT_FAILURE
// and _CPPUNWIND are defined then no exceptions are thrown inside the TDD_VERIFY macros
#if defined(_TDD_NO_RETURN_ON_ASSERT_FAILURE) || defined(_CPPUNWIND)
    #define TDD_VERIFY_RETURN_ON_FAILURE
    #define TDD_VERIFY_RETURN
#else
    #define TDD_VERIFY_RETURN_ON_FAILURE {if (false == __tdd_b) return;}
    #define TDD_VERIFY_RETURN return
#endif

#define TDD_VERIFY(arg)                  do { bool __tdd_b = (arg); \
                                             TDD::Verifier::Verify(__LINE__, __FILE__, __tdd_b, "TDD_VERIFY("#arg")"); \
                                             TDD_VERIFY_RETURN_ON_FAILURE; \
                                         } while(false)
#define TDD_VERIFY_EQUAL(arg1, arg2)     do { bool __tdd_b = ((arg1) == (arg2)); \
                                             TDD::Verifier::Verify(__LINE__, __FILE__, __tdd_b, "TDD_VERIFY_EQUAL("#arg1", "#arg2")"); \
                                             if (false == __tdd_b) return; \
                                         } while(false)
#define TDD_VERIFY_NOT_EQUAL(arg1, arg2) do { bool __tdd_b = ((arg1) != (arg2)); \
                                             TDD::Verifier::Verify(__LINE__, __FILE__, __tdd_b, "TDD_VERIFY_NOT_EQUAL("#arg1", "#arg2")"); \
                                             TDD_VERIFY_RETURN_ON_FAILURE; \
                                         } while(false)
#define TDD_VERIFY_HRESULT(arg)          do { long __tdd_hr = (arg); \
                                              bool __tdd_b = __tdd_hr >= 0; \
                                             TDD::Verifier::Verify(__LINE__, __FILE__, __tdd_b, "TDD_VERIFY_HRESULT("#arg")"); \
                                             TDD_VERIFY_RETURN_ON_FAILURE; \
                                         } while(false)
#define TDD_FAILURE(errorString)         do { TDD::Verifier::Verify(__LINE__, __FILE__, false, errorString); TDD_VERIFY_RETURN; } while(false)
#define TDD_EXCEPTION(errorString)       do { TDD::Verifier::Verify(__LINE__, __FILE__, false, errorString); TDD_VERIFY_RETURN; } while(false)

// backwards-compatibility stuff
#define TEST(group, test) \
struct group##test : public TDD::ClassRegistrarBase, private TDD::TestClassBase \
{ \
    virtual void RunClassTests(TDD::Discriminator& d, TDD::Reporter& r) \
    { \
        TDD::UnitTestInfo uti(#group, #test); \
        if (d.WantTest(uti)) { \
            r.ForEachTest(uti); \
            class TryCatchTest : public TDD::TryCatch \
            { \
                group##test * m_pThis; \
                virtual void Execute() { m_pThis->RunThisTest(); } \
            public: \
                TryCatchTest(group##test* pThis, TDD::Reporter& r, const char * group) \
                    : m_pThis(pThis), TDD::TryCatch(r, group) {} \
            } tct(this, r, #group); \
            tct.TryCatchAndReport( #test, "unknown exception:  continuing anyway"); \
        } \
    } \
    void RunThisTest(); \
} group##test##_var; \
void group##test::RunThisTest()

#endif